<?php
/**
 * Copyright 2008 francissem (http://www.webmoss.net)(francissem@gmail.com)
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author francissem
 * @copyright 2008 francissem (http://www.webmoss.net)(francissem@gmail.com)
 * @license http://www.apache.org/licenses/LICENSE-2.0
 */
class Core_Repository_FileSystem extends Core_Repository_Abstract{
	private $location;
	private $repos_info_location;
	private $repos_copies_location;
	private $curr_cursor;
	private $sack_save=array();
	private $sack_remove=array();
	private $curr_hash;
	private $restrict;
	private $cache_acquire=array();
	
	function __construct($restrict,$location){
		parent::__construct($restrict);
		$this->location=str_replace("\\","/",$location);
		$this->repos_info_location=pathjoin($this->location,"_repos/index");
		$this->repos_copies_location=pathjoin($this->location,"_repos/copies");
		$this->restrict=$restrict;
		
	}

	function isExists(){
		static $is_exists;
		if(!isset($is_exists)){
			$is_exists = is_dir($this->location);
		}
		return $is_exists;
	}

	function build(){
		if(!$this->isExists()){
			Core_System::MakeDirectory($this->location);
			Core_System::MakeDirectory($this->repos_info_location);
		}
	}

	function selectCursor($c){
		if($this->curr_cursor!=$c){
			$this->curr_cursor=$c;
			$this->curr_hash=null;
		}
	}

	function getCurrCursor(){
		return $this->curr_cursor;
	}

	function getCurrHash(){
		return $this->curr_cursor;
		if(!isset($this->curr_hash)){
			$this->curr_hash=hash("crc32b",$this->location."/".$this->curr_cursor);
		}
		return $this->curr_hash;
	}

	function getCursorLastModifiedTime(){
		if(!isset($this->curr_cursor)) throw new Core_Exception("No cursor");
		$file_location = pathjoin($this->location,$this->curr_cursor);
		if(!file_exists($file_location))return 0;
		return filemtime($file_location);
	}
	
	function reset(){
		$this->sack_save=array();
		$this->sack_remove=array();
		$this->curr_cursor=null;
		$this->curr_hash=null;
		$this->cache_acquire=array();
	}

	function saveDataObject(Core_Repository_Data_Abstract $data,$tag="_notag_"){
		$c="Core_Repository_Data_New_{$this->restrict}";
		if(!$data instanceof $c){throw new Core_Repository_Exception("Restructed Data Class Error",Core_Repository_Exception::ERROR_RESTRICTED_DATA_CLASS);}
		if(!isset($this->curr_cursor)) throw new Core_Repository_Exception("No cursor",Core_Repository_Exception::ERROR_NO_CURSOR);
		if(!isset($tag)) $tag="_notag_";
		$t=$this->sack_save[$this->getCurrHash()]["tags"];
		
		if(is_string($tag)){
			$t=explode(",",$tag);
			
		}
		if(is_array($tag)){
			$t=$tag;
		}
			
		$this->sack_save[$this->getCurrHash()]=array(
			"location"=>$this->location."/".$this->curr_cursor,
			"data"=>$data,
			"tags"=>$t
		);
		
	}
	
	function acquireDataObject(){
		if(!isset($this->curr_cursor)) throw new Core_System_Exception("No cursor");
		$hash = $this->getCurrHash();
		if(isset($this->sack_save[$hash])){
			return $this->sack_save[$hash]["data"];
		}else{
			$file_location = $this->location."/".$this->curr_cursor;
			if(!is_file($file_location)) return new Core_Repository_Data_Empty();
			//static $readcache;
			if(!isset($this->cache_acquire[$hash])){
				$rinfo = $this->readReposDataInfo();
				$data_class = "Core_Repository_Data_Repos_".$rinfo->data_class_name;
				$this->cache_acquire[$hash] = new $data_class($rinfo,$this);
			}
			return $this->cache_acquire[$hash];
		}
	}

	function readContent($tag="_notag_",$timestamp=0){
		if(!isset($tag)) $tag="_notag_";
		if(!isset($this->curr_cursor)) throw new Core_System_Exception("No cursor");
		$hash = $this->getCurrHash();
		$rinfo = $this->readReposDataInfo();
		
		if($rinfo->has_copies){
			if($timestamp>0){
				//@todo implement acquireDataObject when specify a timestamp
				$dir = $this->repos_copies_location."/{$hash}/{$tag}";
				if (!is_dir($dir)) {
					throw new Core_Repository("Directory {$dir} not exists");
				}
				$files = scandir($dir);
				$c_diff=0xFFFFFFFF;
				$c_file=null;
				foreach($files as $f){
					if($f!="_last_" && preg_match("/[0-9]*/",$f)){
						if(($d=$timestamp-intval($f))>0){
							if($c_diff>$d){
								$c_diff=$d;
								$c_file=$f;
							}
						}else{
							break;
						}
					}
				}
				
				if(isset($c_file)){
					$copies_file = $dir."/$c_file";
					return file_get_contents($copies_file);	
				}
				
				/*$l = $this->repos_copies_location."/{$tag}/{$hash}";
				if(!file_exists($f =($l."/$timestamp"))){
					$f = closest_value_before($timestamp,array_map("basename",glob($l."/*")),time());
				}
				return file_get_contents($l."/$f");*/
			}elseif($tag!="_notag_"){
				
				$hash = $this->getCurrHash();
				$last_copies_file = $this->repos_copies_location."/{$hash}/{$tag}/_last_";
				
				if(file_exists($last_copies_file)){
					return file_get_contents($last_copies_file);
				}
			}
		}
		return file_get_contents($this->location."/".$this->getCurrCursor());
	}

	function remove(){
		if(!isset($this->curr_cursor)) throw new Core_System_Exception("No cursor");
		if(!isset($this->sack_save[$this->curr_cursor])) unset($this->sack_save[$this->curr_cursor]);
		$file_location = pathjoin($this->location,$this->curr_cursor);
		if(file_exists($file_location))
		Core_System::RemoveFile($file_location);
	}

	public function writeReposDataInfo($hash,Core_Repository_Data_Info $info){
		// @todo implement Core_System_File

		file_put_contents($this->repos_info_location."/".$hash,serialize(Core_Repository_Data_Info::wrap($info)),LOCK_EX);
	}

	public function readReposDataInfo($hash=null){
		static $rinfos;
		if(!isset($hash)){
			$hash = $this->getCurrHash();
		}
		if(!isset($rinfos[$hash])) {
			$l=$this->repos_info_location."/".$hash;
			if(!file_exists($l)) return null;
			$rinfos[$hash]=Core_Repository_Data_Info::unwrap(unserialize(file_get_contents($this->repos_info_location."/".$hash)));
		}
		return $rinfos[$hash];
	}

	function commit(){
		$this->build();

		foreach($this->sack_save as $hash=>$d){
			$file_location = $d["location"];
			$data_object = $d["data"];
			$method = $d["method"];

			$nrinfo = new Core_Repository_Data_Info(array(
				"data_class_name"=>preg_replace("/.*_([A-Za-z0-9]*)$/","$1",get_class($data_object)), //data class
				"has_copies"=>$data_object->isAbleDataCopies(),//able data copies or not,
				"cursor"=>$this->getCurrCursor()
			));

			Core_System::MakeDirectory($this->repos_info_location);
			foreach($d["tags"] as $tag){
				//new data
				
				if($data_object instanceof Core_Repository_Data_New_Abstract){
					$ts = time();						
					if($data_object instanceof Core_Repository_Data_New_Object){
						$content = $data_object->wrap($data_object->getContent());
						$n_data_hash = hash("crc32b",$content);
						$orinfo = $this->readReposDataInfo($hash);
						if($orinfo==null || $orinfo->getLastHashByTag($tag) != $n_data_hash) {
							
							$nrinfo->applyLastTag($tag,$n_data_hash);
							
							if($nrinfo->has_copies){
								if($orinfo==null || $orinfo->getLastHashByTag($tag)!=$nrinfo->getLastHashByTag($tag)){
									
									if(isset($orinfo->last_by_tags)){
										$nrinfo->last_by_tags=array_merge($orinfo->last_by_tags,$nrinfo->last_by_tags);
									}
									$file_copies_dir = $this->repos_copies_location."/{$hash}/{$tag}";
									Core_System::MakeDirectory($file_copies_dir);
									//copy($file_location,$file_copies_dir."/".$ts);
									file_put_contents($file_copies_dir."/".$ts,$content,LOCK_EX);
									
									//and copy to fast path
									Core_System::MakeDirectory($file_copies_dir);
									copy($file_copies_dir."/".$ts,$file_copies_dir."/_last_");
									$this->writeReposDataInfo($hash,$nrinfo);
								}
								if($tag=="_notag_"){
									copy($file_copies_dir."/_last_",$file_location);
								}
							}else{
								file_put_contents($file_location,$content,LOCK_EX);
								$nrinfo->applyLastTag("_notag_",$ts,$n_data_hash);		
								$this->writeReposDataInfo($hash,$nrinfo);
							}
						}
					}
					if($data_object instanceof Core_Repository_Data_New_File){
						//@todo implement new file repository 
					}
				}
				
				//Existing data
				if($data_object instanceof Core_Repository_Data_Repos_Abstract){
					//@todo implement Repository copy to Repository process
				}

			}




		}
	}

}
?>